﻿using System;
using System.Windows;
using Microsoft.Xna.Framework;

namespace Babylon.Toolbox
{
    public class OrbitCamera : CameraBase
    {
        float alpha;
        float beta;
        float radius;
        Vector3 position;
        Vector3 target;
        Matrix view;

        public Vector3 Target
        {
            get { return target; }
            set
            {
                if (target == value)
                    return;

                target = value;

                UpdateViewMatrix();
                RaisePropertyChanged(() => Target);
            }
        }

        public override Vector3 Position
        {
            get { return position; }
            set
            {
                if (position == value)
                    return;

                position = value;

                UpdateViewMatrix();
                RaisePropertyChanged(() => Position);
            }
        }

        public float TargetX
        {
            get { return target.X; }
            set
            {
                if (target.X == value)
                    return;

                target.X = value;

                UpdateViewMatrix();
                RaisePropertyChanged(() => TargetX);
            }
        }

        public float TargetY
        {
            get { return target.Y; }
            set
            {
                if (target.Y == value)
                    return;

                target.Y = value;

                UpdateViewMatrix();
                RaisePropertyChanged(() => TargetY);
            }
        }

        public float TargetZ
        {
            get { return target.Z; }
            set
            {
                if (target.Z == value)
                    return;

                target.Z = value;

                UpdateViewMatrix();
                RaisePropertyChanged(() => TargetZ);
            }
        }

        public float Alpha
        {
            get { return alpha; }
            set
            {
                if (alpha == value)
                    return;

                alpha = value;
                ComputePosition();
                RaisePropertyChanged(() => Alpha);
            }
        }

        public float Beta
        {
            get { return beta; }
            set
            {
                if (beta == value)
                    return;
                beta = value;

                if (beta >= Math.PI)
                    beta = (float)Math.PI - AngularSpeed;

                if (beta < 0)
                    beta = AngularSpeed;

                ComputePosition();
                RaisePropertyChanged(() => Beta);
            }
        }

        public float InertialAlpha { get; set; }
        public float InertialBeta { get; set; }
        public float Inertia { get; set; }

        public float Radius
        {
            get { return radius; }
            set
            {
                if (radius == value)
                    return;

                radius = value;

                if (radius < 0)
                    radius = 0;

                ComputePosition();
                RaisePropertyChanged(()=>Radius);
            }
        }

        public float AngularSpeed { get; set; }


        public OrbitCamera(string name, Guid id)
            : base(name, id)
        {
            target = Vector3.Zero;
            Radius = 15;
            Beta = (float)(Math.PI / 2);
            AngularSpeed = 0.001f;

            Inertia = 0.9f;
        }

        public OrbitCamera(string name = "Orbit Camera")
            : this(name, Guid.NewGuid())
        {
        }

        void UpdateViewMatrix()
        {
            view = Matrix.CreateLookAt(Position, Target, Vector3.Up);
        }

        public void ApplyInertia()
        {
            Alpha += InertialAlpha;
            Beta += InertialBeta;
            InertialAlpha *= Inertia;
            InertialBeta *= Inertia;
        }

        public override Matrix View
        {
            get
            {
                return view;
            }
        }

        void ComputePosition()
        {
            var cosa = (float)Math.Cos(Alpha);
            var sina = (float)Math.Sin(Alpha);
            var cosb = (float)Math.Cos(Beta);
            var sinb = (float)Math.Sin(Beta);

            position = Target + new Vector3(Radius * cosa * sinb, Radius * cosb, Radius * sina * sinb);
            UpdateViewMatrix();
        }

        public void AttachTo(FrameworkElement element)
        {
            bool mouseDown = false;
            Point startPosition = new Point();

            element.MouseLeftButtonDown += (s, e) =>
            {
                element.CaptureMouse();
                mouseDown = true;
                startPosition = e.GetPosition(element);
            };
            element.MouseLeftButtonUp += (s, e) =>
            {
                element.ReleaseMouseCapture();
                mouseDown = false;
            };
            element.MouseMove += (s, e) =>
            {
                if (!mouseDown)
                    return;

                Point currentPosition = e.GetPosition(element);
                InertialAlpha += (float)(currentPosition.X - startPosition.X) * AngularSpeed;
                InertialBeta -= (float)(currentPosition.Y - startPosition.Y) * AngularSpeed;


                startPosition = currentPosition;
            };
            element.MouseWheel += (s, e) =>
            {
                Radius -= e.Delta * Radius / 1000.0f;
            };
        }
    }
}
